﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Collections;
using System.Net;

using SharpPcap;
//using PacketDotNet;
using DevInstinct.Patterns;

namespace test
{
    public class NetStats
    {
        class LinkStat
        {
            public System.Net.Sockets.ProtocolType _proto;
            public IPEndPoint _remote;
            public int _sent;
            public int _rcvd;
        }

        protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private Hashtable _hash = new Hashtable();
        System.Threading.Timer _timer = new System.Threading.Timer(TimerCallback);

        private static void TimerCallback(object state)
        {
            NetStats.Instance.Print(100*1024);
        }

        public bool IsLocalAddress(IPAddress addr)
        {
            IPHostEntry ipEntry = Dns.GetHostEntry(Dns.GetHostName());
            for (int i = 0; i < ipEntry.AddressList.Length; ++i)
                if (ipEntry.AddressList[i].Equals(addr))
                    return true;
            return false;
        }
        
        public void AddPacket(System.Net.Sockets.ProtocolType ProtocolType, IPEndPoint src, IPEndPoint dst, int len, DateTime tm)
        {
            if ((System.Net.Sockets.ProtocolType.Tcp != ProtocolType) && (System.Net.Sockets.ProtocolType.Udp != ProtocolType))
                return;

            LinkStat link = new LinkStat();
            bool bSendPacket = IsLocalAddress(src.Address);

            link._proto = ProtocolType;
            if (bSendPacket)
            {
                link._remote = dst;
                link._sent = len;
            }
            else
            {
                link._remote = src;
                link._rcvd = len;
            }

            lock (_hash)
            {
                //Console.WriteLine("[{0}] Len={1,6} \t {2,15}:{3,5} -> {4,15}:{5,5}",
                //    tm.ToLocalTime().ToString("HH:mm:ss.fff"), len, src_ip, src_port, dst_ip, dst_port);
                int key = link._remote.GetHashCode();
                
                if (_hash.ContainsKey(key))
                {
                    LinkStat value = (LinkStat)_hash[key];
                    value._sent += link._sent;
                    value._rcvd += link._rcvd;
                }
                else
                {
                    _hash.Add(key, link);
                }
            }
        }

        public void Print(int minsize)
        {
            int count = 0;
            log.Warn("---------------------------------------------------------------------------------{");
            log.WarnFormat(" hash.count = {0}", _hash.Count);
            lock (_hash)
            {
                IDictionaryEnumerator it = _hash.GetEnumerator();
                while (it.MoveNext())
                {
                    LinkStat link = (LinkStat)it.Value;
                    int transferredBytes = link._sent + link._rcvd;
                    if (transferredBytes >= minsize)
                    {
                        log.DebugFormat("[{0,12}] proto={1}, remote={2,18}, {3,6}, sent={4,10:#,##0}KB, rcvd={5,10:#,##0}KB, total={6,10:#,##0}KB "
                                , it.Key
                                , link._proto.ToString(), link._remote.Address.ToString(), link._remote.Port
                                , link._sent / 1024, link._rcvd / 1024, transferredBytes / 1024);
                        ++count;
                    }
                }
            }
            log.WarnFormat(" count = {0} / {1} (min={2:#,##0} KB)", count, _hash.Count, minsize/1024);
            log.Warn("---------------------------------------------------------------------------------}");
        }

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static NetStats()
        {
        }

        NetStats()
        {
            _timer.Change(0, 30*1000);
        }

        public static NetStats Instance
        {
            get
            {
                return Singleton<NetStats>.Instance;
            }
        }
    }

    class PilotSharpPcap
    {
        protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private CaptureDeviceList _devices;
        private ICaptureDevice _device;

        public bool OpenDevice()
        {
            string ver = SharpPcap.Version.VersionString;
            log.InfoFormat("SharpPcap.Version: {0} \n", ver);

            _devices = CaptureDeviceList.Instance;

            if (_devices.Count < 1)
            {
                log.Error("No device found on this machine");
                CloseDevice();
                return false;
            }

            Console.WriteLine("The following devices are available on this machine:");
            Console.WriteLine("----------------------------------------------------");
            Console.WriteLine();

            int i = 0;
            foreach (var dev in _devices)
            {
                Console.WriteLine("{0}) {1} {2}", i, dev.Name, dev.Description);
                i++;
            }

            do
            {
                Console.WriteLine();
                Console.Write("-- Please choose a device to capture: ");
                i = int.Parse(Console.ReadLine());
            }
            while ((0 > i) || (_devices.Count <= i));

            _device = _devices[i];
            log.InfoFormat("Target device: {0} {1}", _device.Name, _device.Description);

            return true;
        }
        public void CloseDevice()
        {
            _device = null;
            _devices = null;
        }
        public bool IsOpenDevice()
        {
            return (null != _device);
        }

        public bool TCPDump_TEST()
        {
            if (!IsOpenDevice())
                return false;

            int readTimeoutMilliseconds = 1000;
            _device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrival);
            _device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);
            //_device.Filter = "ip and tcp";
            //_device.Filter = "ip and (tcp or udp)";


            if (_device is SharpPcap.AirPcap.AirPcapDevice)
            {
                // NOTE: AirPcap devices cannot disable local capture
                var airPcap = _device as SharpPcap.AirPcap.AirPcapDevice;
                airPcap.Open(SharpPcap.WinPcap.OpenFlags.DataTransferUdp, readTimeoutMilliseconds);
            }
            else if (_device is SharpPcap.WinPcap.WinPcapDevice)
            {
                var winPcap = _device as SharpPcap.WinPcap.WinPcapDevice;
                winPcap.Open(SharpPcap.WinPcap.OpenFlags.DataTransferUdp | SharpPcap.WinPcap.OpenFlags.NoCaptureLocal, readTimeoutMilliseconds);
            }
            else if (_device is SharpPcap.LibPcap.LibPcapLiveDevice)
            {
                var livePcapDevice = _device as SharpPcap.LibPcap.LibPcapLiveDevice;
                livePcapDevice.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);
            }
            else
            {
                throw new System.InvalidOperationException("unknown device type of " + _device.GetType().ToString());
            }

            Console.WriteLine();
            Console.WriteLine("-- The following tcpdump filter will be applied: \"{0}\"", _device.Filter);
            Console.WriteLine("-- Listening on {0}, hit 'Enter' to stop...", _device.Description);

            _device.StartCapture();
            Console.ReadLine();

            _device.StopCapture();
            Console.WriteLine(_device.Statistics.ToString());

            _device.Close();
            Console.ReadKey();

            return true;
        }
        public bool TCPDump()
        {
            if (!IsOpenDevice())
                return false;

            int readTimeoutMilliseconds = 1000;
            _device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrival);
            _device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds);
            //_device.Filter = "ip and tcp";
            //_device.Filter = "ip and (tcp or udp)";

            Console.WriteLine();
            Console.WriteLine("-- The following tcpdump filter will be applied: \"{0}\"", _device.Filter);
            Console.WriteLine("-- Listening on {0}, hit 'Ctrl-C' to exit...", _device.Description);

            // Start capture 'INFINTE' number of packets
            _device.Capture();

            // Close the pcap device
            // (Note: this line will never be called since
            //  we're capturing infinite number of packets
            _device.Close();

            return true;
        }
        /// <summary>
        /// Prints the time, length, src ip, src port, dst ip and dst port
        /// for each TCP/IP packet received on the network
        /// </summary>
        private void device_OnPacketArrival(object sender, CaptureEventArgs e)
        {
            DateTime time = e.Packet.Timeval.Date;
            int len = e.Packet.Data.Length;

            var packet = PacketDotNet.Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data);
            if ((packet is PacketDotNet.EthernetPacket) == false)
                return;

            var ip = PacketDotNet.IpPacket.GetEncapsulated(packet);
            if (null == ip) return;

            if (PacketDotNet.IpVersion.IPv4 != ip.Version)
                return;

            System.Net.Sockets.ProtocolType ProtocolType = System.Net.Sockets.ProtocolType.Unknown;
            IPEndPoint src, dst;

            switch (ip.Protocol)
            {
                case PacketDotNet.IPProtocolType.TCP:
                    ProtocolType = System.Net.Sockets.ProtocolType.Tcp;
                    var tcp = PacketDotNet.TcpPacket.GetEncapsulated(packet);
                    src = new IPEndPoint(ip.SourceAddress, tcp.SourcePort);
                    dst = new IPEndPoint(ip.DestinationAddress, tcp.DestinationPort);
                    NetStats.Instance.AddPacket(ProtocolType, src, dst, len, time);
                    break;

                case PacketDotNet.IPProtocolType.UDP:
                    ProtocolType = System.Net.Sockets.ProtocolType.Udp;
                    var udp = PacketDotNet.UdpPacket.GetEncapsulated(packet);
                    src = new IPEndPoint(ip.SourceAddress, udp.SourcePort);
                    dst = new IPEndPoint(ip.DestinationAddress, udp.DestinationPort);
                    NetStats.Instance.AddPacket(ProtocolType, src, dst, len, time);
                    break;
            }
        }
        private void device_OnPacketArrival2(object sender, CaptureEventArgs e)
        {
            DateTime time = e.Packet.Timeval.Date;
            int len = e.Packet.Data.Length;

            PacketDotNet.Packet packet = PacketDotNet.Packet.ParsePacket(e.Packet.LinkLayerType, e.Packet.Data);
            if (false){
                PacketDotNet.IpPacket ipPacket = PacketDotNet.IpPacket.GetEncapsulated(packet);
                if (PacketDotNet.IPProtocolType.TCP != ipPacket.Protocol)
                    log.DebugFormat("[@@@] {0}", ipPacket.Protocol.ToString());
            }
            PacketDotNet.TcpPacket tcpPacket = PacketDotNet.TcpPacket.GetEncapsulated(packet);
            if (tcpPacket != null)
            {
                PacketDotNet.IpPacket ipPacket = (PacketDotNet.IpPacket)tcpPacket.ParentPacket;
                IPEndPoint src = new IPEndPoint(ipPacket.SourceAddress, tcpPacket.SourcePort);
                IPEndPoint dst = new IPEndPoint(ipPacket.DestinationAddress, tcpPacket.DestinationPort);

                switch (ipPacket.Protocol)
                {
                    case PacketDotNet.IPProtocolType.TCP:
                        NetStats.Instance.AddPacket(System.Net.Sockets.ProtocolType.Tcp, src, dst, len, time);
                        break;
                    case PacketDotNet.IPProtocolType.UDP:
                        NetStats.Instance.AddPacket(System.Net.Sockets.ProtocolType.Udp, src, dst, len, time);
                        break;
                }
            }
        }
    }
}

namespace NetStatistics
{
    class Program
    {
        protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        static void Initialize()
        {
            log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo("NetStatistics.log4net.xml"));
        }

        static void Main(string[] args)
        {
            bool result = false;

            Initialize();

            log.Info("-------------------------------------------");

            test.PilotSharpPcap pcap = new test.PilotSharpPcap();
            result = pcap.OpenDevice();
            result = pcap.TCPDump();

            log.Info("-------------------------------------------");
            log.Info("Press ENTER to exit...");
            Console.ReadKey();
        }
    }
}
